////////////////////////////////////////////////////////////////////////////////

#include "client_network.h"

////////////////////////////////////////////////////////////////////////////////

#include <boost/bind.hpp>
#include "client.h"
#include <iostream>

////////////////////////////////////////////////////////////////////////////////


ClientNetwork::ClientNetwork( const std::string& _port,
                              Client* _client,
                              boost::asio::io_service& _io_service )
: m_io_service(_io_service),
  m_Socket(m_io_service),
  m_Client(_client)
{
    std::string serverName = "";
    do 
    {
        // Get server name
        std::cout << "Server: ";    
        std::cin >> serverName;

    } while(!Connect(serverName, _port));
}


////////////////////////////////////////////////////////////////////////////////


ClientNetwork::~ClientNetwork()
{
}


////////////////////////////////////////////////////////////////////////////////


bool ClientNetwork::Connect( const std::string& _serverAddr, const std::string& _port )
{
    try
    {
        // Resolve host address
        tcp::resolver::query query( _serverAddr.c_str(), _port.c_str() );

        // Boost tcp address resolver
        tcp::resolver resolver(m_io_service);

        tcp::resolver::iterator endpointIterator = resolver.resolve(query);


        tcp::resolver::iterator end;
        boost::system::error_code error = boost::asio::error::host_not_found;

        while (error && endpointIterator != end)
        {
            m_Socket.close();
            m_Socket.connect(*endpointIterator++, error);
        }
        if (error)
            throw boost::system::system_error(error);
        else
        {
            // Read client ID
            int clientIDSize = m_Socket.read_some( boost::asio::buffer(m_RecvBuffer) );
            assert( clientIDSize == sizeof(char) );

            // Set client ID
            m_Client->SetID( m_RecvBuffer[0] );

            // Synchronize time
            clock_t serverTime, clientTime;
            clientTime = clock();

            size_t timeSize = m_Socket.read_some( boost::asio::buffer(m_RecvBuffer) );
            assert( timeSize == sizeof(clock_t) );
            memcpy( &serverTime, m_RecvBuffer.data(), sizeof(clock_t) );
            m_Client->SyncronizeTime(serverTime, clientTime);

            // Start receive
            Receive();
        }

        std::cout<< "Connected" <<std::endl;
        return true;
    }
    catch (...)
    {
        std::cout<< "Cannot connect to the server " << _serverAddr << ". Try again."<<std::endl;
        return false;
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientNetwork::Send( const ClientPacket& _packet )
{
    bool sendNow = m_SendQueue.empty();
    m_SendQueue.push(_packet);

    // If there are some packets in queue, just push the packet
    // It will be sent automatically
    if (sendNow)
        StartSend();
}


////////////////////////////////////////////////////////////////////////////////


void ClientNetwork::StartSend()
{
    // Send packet
    m_SendBuffer = m_SendQueue.front().ToStr();
    m_Socket.async_write_some( boost::asio::buffer(m_SendBuffer),
                               boost::bind( &ClientNetwork::HandleSend, this,
                                             boost::asio::placeholders::error,
                                             boost::asio::placeholders::bytes_transferred ) );
}


////////////////////////////////////////////////////////////////////////////////


void ClientNetwork::HandleSend( const boost::system::error_code& _error,
                                size_t _bytesReceived )
{
    if (!_error)
    {
        // Continue
        m_SendQueue.pop();
        if ( !m_SendQueue.empty() )
            StartSend();
    }
    else
        // Stop on error
        m_Socket.close();
}


////////////////////////////////////////////////////////////////////////////////


void ClientNetwork::Receive()
{
    m_Socket.async_read_some( boost::asio::buffer(m_RecvBuffer),
                              boost::bind( &ClientNetwork::HandleReceive,
                                           this,
                                           boost::asio::placeholders::error,
                                           boost::asio::placeholders::bytes_transferred) );
}


////////////////////////////////////////////////////////////////////////////////


void ClientNetwork::HandleReceive( const boost::system::error_code& _error,
                                   size_t _bytesReceived )
{
    if (!_error)
    {
        size_t bytesLeft = _bytesReceived;
        size_t offset = 0;
        while (bytesLeft > 0)
        {
            ServerPacket packet;
            packet.FromStr(m_RecvBuffer.data() + offset);

            offset += packet.Size();
            bytesLeft -= packet.Size();

            m_Client->Receive(packet);
        }
        Receive();
    }
    else
        m_Socket.close();
}


////////////////////////////////////////////////////////////////////////////////
